﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Input;

namespace WindowIconizer
{
    class globalKeyboardHook
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
        //callback delegate is made a member variable of the class to prevent the GC from collecting it
        //otherwise this delegate could be deleted by the GC, since no object is keeping a reference to it
        //it is after all only used by a function that comes from an unmanaged dll
        private static LowLevelKeyboardProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;

        //disable constructor
        private globalKeyboardHook()
        {
        }
        public static void start()
        {
            _hookID = SetHook(_proc);
        }
        public static void stop()
        {
            UnhookWindowsHookEx(_hookID);
        }

        //set callbacks
        private static callbackFromKeyboard callGUI = null;
        public static void setKeyboardCallback(callbackFromKeyboard callback)
        {
            callGUI = callback;
        }

        private static IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                const int WH_KEYBOARD_LL = 13;
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }
        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            const int WM_KEYDOWN = 0x0100;
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                Key key = KeyInterop.KeyFromVirtualKey(vkCode);
                onKeyDown(key);
            }

            const int WM_KEYUP = 0x0101;
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                Key key = KeyInterop.KeyFromVirtualKey(vkCode);
                onKeyUp(key);
            }

            if (isCtrlShiftQReached() && callGUI != null)
            {
                callGUI(true);
            }
            else
            {
                callGUI(false);
            }

            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }

        private static HashSet<int> keysDown = new HashSet<int>();
        private static void onKeyDown(Key key)
        {
            //q = 60
            //leftShift = 116
            //rightShift = 117
            //leftCtrl = 118
            //rightCtrl = 119
            int k = (int)key;
            if (k == 60 || k == 116 || k == 117 || k == 118 || k == 119)
            {
                keysDown.Add(k);
            }
        }
        private static void onKeyUp(Key key)
        {
            //q = 60
            //leftShift = 116
            //rightShift = 117
            //leftCtrl = 118
            //rightCtrl = 119
            int k = (int)key;
            if (k == 60 || k == 116 || k == 117 || k == 118 || k == 119)
            {
                if (keysDown.Contains(k))
                {
                    keysDown.Remove(k);
                }
            }
        }
        private static bool isCtrlShiftQReached()
        {
            bool reached = true;
            reached &= (keysDown.Contains(60));
            reached &= (keysDown.Contains(116) || keysDown.Contains(117));
            reached &= (keysDown.Contains(118) || keysDown.Contains(119));
            return reached;
        }
    }
}
